#include "stdafx.h"
#include <stdlib.h>
#include <assert.h>

#include "xmlstruct.h"
#include "xmlscan.h"
#include "xmlparse.h"

#include "../TracerDLL.h"
static void DeleteXmlContent (LPXmlContent pContent);


CXMLParser::CXMLParser (int nBufSize) : 
   m_scScanner (nBufSize),
   m_arrayContents (INIT_SIZE)
{
   m_pPI             = NULL;
   m_nCount          = 0;
   m_nPrevTokenType  = -1;
   m_bRootFound      = FALSE;
}

CXMLParser::~CXMLParser ()
{
   if (m_pPI) {
      free (m_pPI);
      DBG_DelMem (m_pPI, "CXMLParser::~CXMLParser m_pPI freed.");
   }
      
   for (int i = 0; i <= m_nCount; i++) {
      /* In case parsing fails and the last element is CDATA,
       *  m_arrayContent[m_nCount] is not NULL and need to be freed.
       */
      if (m_arrayContents[i]) {
         DeleteXmlContent (m_arrayContents[i]);
      }
   }
}
   
int32 CXMLParser::OpenFile (char* pszFilename)
{
   assert (pszFilename);
   
   return m_scScanner.OpenFile (pszFilename);
}

void CXMLParser::CloseFile ()
{
   m_scScanner.CloseFile ();
}

int32 CXMLParser::GetNextInPreorder (LPXmlContent* ppXmlContent)
{
   int32          nTokenType;
   LPXmlContent   pContent = NULL;
   int32          nRet;
   int32          nTokenLen;
   
   *ppXmlContent = NULL;
   if (m_pPI) {
      DeleteXmlContent (m_pPI);
      m_pPI = NULL;
   }
   
   while (pContent == NULL) {
      if (m_nCount == 0) {
         // No root elemenmt yet.
         nTokenType = m_scScanner.GetToken ();
      } else {
         if (m_nPrevTokenType == TOKEN_PI ||
             m_nPrevTokenType == TOKEN_CDATA_SECTION ||
             m_nPrevTokenType == TOKEN_ENDING ||
             m_nPrevTokenType == TOKEN_COMMENT ||
             m_nPrevTokenType == '>' ) {
            nTokenType = m_scScanner.GetCDATA ();
         } else if (m_nPrevTokenType == TOKEN_CDATA) {
            nTokenType = m_scScanner.GetToken ();
         } else {
            /* no other token can end an XML content.*/
            assert (0);
         }
      }
      
      if (nTokenType == TOKEN_EOF) {
         if (m_nCount != 0) {
            return E_UNEXPECTED_EOF;
         }
      }   
      if (nTokenType <= 0)
         return nTokenType;
      
      if (nTokenType == TOKEN_CDATA || nTokenType == TOKEN_CDATA_SECTION) {
         if (m_nCount == 0) {
            // No root element yet.
            return E_INVALID_AT_TOP_LEVEL;
         }
         nTokenLen = strlen (m_scScanner.m_pToken);
         if (nTokenLen == 0) {
            m_nPrevTokenType = nTokenType;
         }   
         nRet = OnCDATAContent ();
         if (nRet < 0)
            return nRet;
            
         pContent = m_arrayContents[m_nCount];
         // For CDATA, we don't increment m_nCount, so that
         //  the next content will delete it and ocuppy the
         //  same place.
         m_nPrevTokenType = nTokenType;
      } else if (nTokenType == TOKEN_PI_BEGIN) {
         assert (m_pPI == NULL);
         nRet = OnPI (); 
         
         if (nRet == TOKEN_EOF) {
            nRet = E_UNEXPECTED_EOF;
         }
         if (nRet < 0)
            return nRet;
            
         pContent = m_pPI;
         m_nPrevTokenType = TOKEN_PI;
      } else if (nTokenType == '<') {
         if (m_nCount == 0 && m_bRootFound) {
            return E_2ND_TOP_LEVEL;
         }
         m_bRootFound = TRUE;
         
         nRet = OnElement ();
         if (nRet == TOKEN_EOF) {
            nRet = E_UNEXPECTED_EOF;
         }
         if (nRet < 0)
            return nRet;
         
         pContent = m_arrayContents[m_nCount];
         if (nRet == '>') {
            // It should stay, until the corresponding end tag is found.
            m_nCount ++;
            m_nPrevTokenType = '>';
         } else if (nRet == TOKEN_ENDING) {
            // It has no children, next time it will be deleted.
            m_nPrevTokenType = TOKEN_ENDING;
         }
      } else if (nTokenType == TOKEN_END_TAG_BEGIN) {
         // "</"
         if (m_nCount == 0) {
            return E_UNEXPECTED_END_TAG;
         }
         
         nRet = OnEndTag ();
         if (nRet < 0)
            return nRet;
            
         m_nPrevTokenType = '>';
         if (m_arrayContents[m_nCount]) {
            /* if there is a CDATA before end tag.*/
            DeleteXmlContent(m_arrayContents[m_nCount]);
            m_arrayContents[m_nCount] = NULL;
         }
         /* Delete the element */
         DeleteXmlContent(m_arrayContents[m_nCount-1]);
         m_arrayContents[m_nCount-1] = NULL;
         m_nCount--;
      } else if (nTokenType == TOKEN_COMMENT) {
         m_nPrevTokenType = TOKEN_COMMENT;
      } else if (nTokenType == TOKEN_WHITE_SPACE){
         m_nPrevTokenType = TOKEN_WHITE_SPACE;
      } else {
         return E_UNEXPECTED_TOKEN;
      }
   }   
   *ppXmlContent = pContent;
   return 0;
}

int32 CXMLParser::GetLine ()
{
   return m_scScanner.GetLineNo ();
}

int32 CXMLParser::GetCol ()
{
   return m_scScanner.GetColNo ();
}

int32 CXMLParser::AddContentToArray (LPXmlContent pContent, int nIdx)
{
   int nRet;
   LPXmlContent pParent = NULL;
    
   assert (nIdx >= 0);
   int nSize = m_arrayContents.GetCount ();
   if (nSize > nIdx) {
      if (m_arrayContents[nIdx])
         DeleteXmlContent (m_arrayContents[nIdx]);
   } else {
      assert (nIdx < nSize+INC_STEP);
      nRet = m_arrayContents.Resize (nSize+INC_STEP);
      if (nRet != 0)
         return nRet;
   }
   m_arrayContents[nIdx] = pContent;
   if (nIdx-1 >= 0) {
      pParent = m_arrayContents[nIdx-1];
   }
   pContent->pParent = pParent;
   return 0;
}

LPXmlContent CXMLParser::NewContent (int nType)
{
   LPXmlContent pContent;
   
   assert (nType == XML_CONTENT_CDATA_TYPE ||
           nType == XML_CONTENT_PI_TYPE ||
           nType == XML_CONTENT_ELEMENT_TYPE);
           
   pContent = (LPXmlContent) malloc (sizeof (XmlContent)); 
   DBG_NewMem (pContent, "CXMLParser::NewContent, pContent malloced.");

   memset (pContent, 0, sizeof (XmlContent));
   pContent->nType = nType;
   pContent->nLine = GetLine ();
   pContent->nColumn = GetCol ();
   return pContent;
}

int32 CXMLParser::GetAttribute (LPXmlAttribute* ppAttr)
{
   int32             nRet;
   char*             pToken = NULL;
   int32             nTokenLen = 0;
   int32             nTokenType;
   LPXmlAttribute    pAttr;
   
   assert (ppAttr);
   pAttr = (LPXmlAttribute) malloc (sizeof (XmlAttribute));
   DBG_NewMem (pAttr, "CXMLParser::GetAttribute, pAttr malloced.");
   if (!pAttr) {
      return E_INSUFFICIENT_MEMORY;
   }

   memset(pAttr, 0, sizeof (XmlAttribute));
   do {
      nTokenType = m_scScanner.GetToken ();
   } while (nTokenType == TOKEN_WHITE_SPACE);
   
   if (nTokenType < 0) {
      nRet = nTokenType;
      goto __GET_ATTR_EXIT__;
   } else if (nTokenType == '>' || nTokenType == TOKEN_ENDING) {
      free (pAttr);
      DBG_DelMem (pAttr, "CXMLParser::GetAttribute, pAttr freed.");

      *ppAttr = NULL;
      return nTokenType;
   } else if (nTokenType != TOKEN_NAME) {
      nRet = E_UNEXPECTED_TOKEN;
      goto __GET_ATTR_EXIT__;
   }
   // Got name;
   pToken = m_scScanner.m_pToken;
   nTokenLen = strlen (pToken);
   pAttr->pName = (char*) malloc (nTokenLen + 1);
   DBG_NewMem (pAttr->pName, "CXMLParser::GetAttribute, pAttr->pName malloced.");

   if (!pAttr->pName) {
      nRet = E_INSUFFICIENT_MEMORY;
      goto __GET_ATTR_EXIT__;
   }
   strcpy (pAttr->pName, pToken);
   
   // '=' wanted.
   do {
      nTokenType = m_scScanner.GetToken ();
   } while (nTokenType == TOKEN_WHITE_SPACE);
   
   if (nTokenType < 0) {
      nRet = nTokenType;
      goto __GET_ATTR_EXIT__;
   } else if (nTokenType != '=') {
      nRet = E_UNEXPECTED_TOKEN;
      goto __GET_ATTR_EXIT__;
   }   
   // Got '='
   do {
      nTokenType = m_scScanner.GetToken ();
   } while (nTokenType == TOKEN_WHITE_SPACE);
   
   if (nTokenType < 0) {
      nRet = nTokenType;
      goto __GET_ATTR_EXIT__;
   } else if (nTokenType != TOKEN_VALUE) {
      nRet = E_UNEXPECTED_TOKEN;
      goto __GET_ATTR_EXIT__;
   }
   pToken = m_scScanner.m_pToken;
   nTokenLen = strlen (pToken);
   pAttr->pValue= (char*) malloc (nTokenLen + 1);
   DBG_NewMem (pAttr->pValue, "CXMLParser::GetAttribute, pAttr->pValue malloced.");

   if (!pAttr->pValue) {
      nRet = E_INSUFFICIENT_MEMORY;
      goto __GET_ATTR_EXIT__;
   }
   strcpy (pAttr->pValue, pToken);
   *ppAttr = pAttr;
   return 0;   

__GET_ATTR_EXIT__:
   assert (pAttr);
   if (pAttr->pName) {
      free (pAttr->pName);
      DBG_DelMem (pAttr->pName, "CXMLParser::GetAttribute, pAttr->pName freed.");
   }
   if (pAttr->pValue) {
      free (pAttr->pValue);
       DBG_DelMem (pAttr->pValue, "CXMLParser::GetAttribute, pAttr->pValue freed.");
  }
   free (pAttr);      
   DBG_DelMem (pAttr, "CXMLParser::GetAttribute, pAttr freed.");
   
   *ppAttr = NULL;
   return nRet;
}


int32 CXMLParser::OnCDATAContent ()
{
   XmlContent* pContent;
   int32       nRet;
   char*       pToken = NULL;
   int32       nTokenLen = 0;
   
   pToken = m_scScanner.m_pToken;
   nTokenLen = strlen (pToken);
   
   pContent = NewContent (XML_CONTENT_CDATA_TYPE);
   if (!pContent) {
      return E_INSUFFICIENT_MEMORY;
   }
   pContent->cdata.pCDATA = (char*) malloc (nTokenLen + 1);
   DBG_NewMem (pContent->cdata.pCDATA, "CXMLParser::OnCDATAContent, pContent->cdata.pCDATA malloced");

   if (!pContent->cdata.pCDATA) {
      DeleteXmlContent (pContent);
      return E_INSUFFICIENT_MEMORY;
   }
   strcpy (pContent->cdata.pCDATA, pToken);
   nRet = AddContentToArray (pContent, m_nCount);

   return nRet;
}

int32 CXMLParser::OnElement ()
{
   CXmlAttrPtrArray  arrayAttr (INIT_SIZE);
   int32             nTokenType;
   XmlContent*       pContent;
   int32             nRet;
   char*             pToken = NULL;
   int32             nTokenLen = 0;
   
   pContent = NewContent (XML_CONTENT_ELEMENT_TYPE); 
   if (!pContent) {
      return E_INSUFFICIENT_MEMORY;
   }
   // Get the element name.
   nTokenType = m_scScanner.GetToken ();
   if (nTokenType != TOKEN_NAME) {
      DeleteXmlContent (pContent);
      return E_INVALID_CHAR_IN_NAME;
   }
   pToken = m_scScanner.m_pToken;
   nTokenLen = strlen (pToken);
   pContent->element.pName = (char*) malloc (nTokenLen + 1);
   DBG_NewMem (pContent->element.pName, "CXMLParser::OnElement, pContent->element.pName malloced");

   if (!pContent->element.pName) {
      DeleteXmlContent (pContent);
      return E_INSUFFICIENT_MEMORY;
   }
   strcpy (pContent->element.pName, pToken);
   DBG_Msg (pContent->element.pName);

   do {
      XmlAttribute* pAttribute = NULL;
      nRet = GetAttribute (&pAttribute);
      if (pAttribute != NULL) {
         int32 nSize = arrayAttr.GetCount ();
         if (pContent->element.nCount >= nSize) {
            assert (nSize + INC_STEP > pContent->element.nCount);
            arrayAttr.Resize (nSize + INC_STEP);
         }
         arrayAttr[pContent->element.nCount] = pAttribute;
         (pContent->element.nCount)++;
      }
   } while (nRet == 0);
   
   pContent->element.ppAttributes = arrayAttr.Detach ();
   if (nRet == '>' || nRet == TOKEN_ENDING) {
      AddContentToArray (pContent, m_nCount);
      return nRet;
   }
   // unexpected token.
   DeleteXmlContent (pContent);
   if (nRet >0) {
      return E_UNEXPECTED_TOKEN;
   }
   return nRet;
}

int32 CXMLParser::OnEndTag ()
{
   int32       nRet;
   char*       pToken = NULL;
   int32       nTokenLen = 0;
   char*       pName;
   int32       nTokenType;
   
   assert (m_nCount > 0);
   nRet = 0;
   nTokenType = m_scScanner.GetToken ();
   if (nTokenType != TOKEN_NAME) {
      return E_INVALID_CHAR_IN_NAME;
   }
   pToken = m_scScanner.m_pToken;
   nTokenLen = strlen (pToken);
   pName = (char*) malloc (nTokenLen + 1);
   DBG_NewMem (pName, "CXMLParser::OnEndTag, pName, malloced.");

   if (!pName) {
      return E_INSUFFICIENT_MEMORY;
   }
   strcpy (pName, pToken);
   do {
      nTokenType = m_scScanner.GetToken ();
   } while (nTokenType == TOKEN_WHITE_SPACE);
   
   if (nTokenType != '>') {
      nRet = E_UNEXPECTED_TOKEN;
      goto _ON_END_TAG_RETURN__;
   }
   
   assert (m_arrayContents[m_nCount-1]->nType == XML_CONTENT_ELEMENT_TYPE);
   if (strcmp (pName, m_arrayContents[m_nCount-1]->element.pName) == 0) {
      nRet = 0;
      goto _ON_END_TAG_RETURN__;
   }
   
   nRet = E_UNMATCHED_END_TAG;
   
_ON_END_TAG_RETURN__:   
   free (pName);
   DBG_DelMem (pName, "CXMLParser::OnEndTag, pName freed.");

   return nRet;
}

int32 CXMLParser::OnPI ()
{
   int32       nTokenType;
   XmlContent* pContent;
   char*       pToken = NULL;
   int32       nTokenLen = 0;
   
   
   pContent = NewContent (XML_CONTENT_PI_TYPE); 
   if (!pContent) {
      return E_INSUFFICIENT_MEMORY;
   }
   // Get the application name.
   nTokenType = m_scScanner.GetToken ();
   if (nTokenType < 0) {
      return nTokenType;
   }
      
   if (nTokenType != TOKEN_NAME) {
      DeleteXmlContent (pContent);
      return E_INVALID_CHAR_IN_NAME;
   }
   pToken = m_scScanner.m_pToken;
   nTokenLen = strlen (pToken);
   pContent->pi.pAppName = (char*) malloc (nTokenLen + 1);
   DBG_NewMem (pContent->pi.pAppName, "CXMLParser::OnPI, pContent->pi.pAppName malloced");

   if (!pContent->pi.pAppName) {
      DeleteXmlContent (pContent);
      return E_INSUFFICIENT_MEMORY;
   }
   strcpy (pContent->pi.pAppName, pToken);
   
   // Get the instruction.
   nTokenType = m_scScanner.GetPI ();
   if (nTokenType < 0) {
      DeleteXmlContent (pContent);
      return nTokenType;
   }

   if (nTokenType != TOKEN_PI) {
      DeleteXmlContent (pContent);
      return E_PI_NOT_CLOSED;
   }
   pToken = m_scScanner.m_pToken;
   nTokenLen = strlen (pToken);
   pContent->pi.pInstruction = (char*) malloc (nTokenLen + 1);
   DBG_NewMem (pContent->pi.pInstruction, "CXMLParser::OnPI, pContent->pi.pInstruction malloced");

   if (!pContent->pi.pInstruction) {
      DeleteXmlContent (pContent);
      return E_INSUFFICIENT_MEMORY;
   }
   strcpy (pContent->pi.pInstruction, pToken);
   if (m_pPI) {
      DeleteXmlContent (pContent);
      return E_2ND_PI; 
   }
   assert (m_pPI == NULL);
   m_pPI = pContent;
   return 0;
}

static void DeleteXmlContent (XmlContent* pContent)
{
   assert (pContent);
   int i = 0;
   switch (pContent->nType)
   {
   case XML_CONTENT_ELEMENT_TYPE:
      // Free the element's name
      if (pContent->element.pName) {
         free (pContent->element.pName);
         DBG_DelMem (pContent->element.pName, "DeleteXmlContent, pContent->element.pName freed.");
      }
      // Free the element's attributes
      for (i = 0; i < pContent->element.nCount ; i++) {
         XmlAttribute* pAttribute = pContent->element.ppAttributes[i];
         if (pAttribute) {
            if (pAttribute->pName) {
               free (pAttribute->pName);
               DBG_DelMem (pAttribute->pName, "DeleteXmlContent, pAttribute->pName freed.");
            }
            if (pAttribute->pValue) {
               free (pAttribute->pValue);
               DBG_DelMem (pAttribute->pValue, "DeleteXmlContent, pAttribute->pValue freed.");
            }
            free (pAttribute);
            DBG_DelMem (pAttribute, "DeleteXmlContent, pAttribute freed.");
         }
      }
      break;
   case XML_CONTENT_CDATA_TYPE:
      if (pContent->cdata.pCDATA) {
         free (pContent->cdata.pCDATA);
         DBG_DelMem (pContent->cdata.pCDATA, "DeleteXmlContent, pContent->cdata.pCDATA freed.");
      }
      break;
   case XML_CONTENT_PI_TYPE:
      if (pContent->pi.pAppName) {
         free (pContent->pi.pAppName);
         DBG_DelMem (pContent->pi.pAppName, "DeleteXmlContent, pContent->pi.pAppName freed.");
      }
      if (pContent->pi.pInstruction) {
         free (pContent->pi.pInstruction);
         DBG_DelMem (pContent->pi.pInstruction, "DeleteXmlContent, pContent->pi.pInstruction freed.");
      }
      break;
   default:
      assert (0);
   }
   free (pContent);
   DBG_DelMem (pContent, "DeleteXmlContent, pContent freed.");
}
